//-------------------------------------------------------------------------------------
// <copyright file="Repair.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation. All rights reserved.
//      THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
//      EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
//      WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// </copyright>
// <author>Greg Beech</author>
// <email>gregbee@microsoft.com</email>
// <date>2004-04-15</date>
// <summary>
//      Repairs an MSI package.
// </summary>  
//-------------------------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Msi
{
    #region Using directives

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml;

    #endregion

    #region Class Comments
    /// <summary>
    /// Repairs an MSI package.
    /// </summary>
    /// <remarks>
    /// <code><![CDATA[<Msi.Repair MsiPath="msiPath" ProductCode="productCode" LogFile="logFile" LogOptions="logOptions" UserInterfaceLevel="userInterfaceLevel" LoadUserProfile="loadUserProfile" RepairOptions="repairOptions" />]]></code>
    /// <para>where:</para>
    /// <para><i>msiPath (Required*)</i></para>
    /// <para>
    /// The path to the MSI to repair.
    /// </para>
    /// <para><i>productCode (Required*)</i></para>
    /// <para>
    /// The GUID product code of the product to repair.
    /// </para>
    /// <para><i>logFile (Required)</i></para>
    /// <para>
    /// The path to the log file for the repair. The recommended file name is the name of the MSI
    /// with the extension *.repair.log.
    /// </para>
    /// <para><i>logOptions</i></para>
    /// <para>
    /// A delimited list of logging options. Possible values are: Default, StatusMessages, Warnings, Errors,
    /// ActionStart, ActionSpecificRequests, UserRequests, InitialParameters, OutOfMemory, TerminalProperties,
    /// Verbose, AppendLog, FlushEachLine, AllInformation. The default is Default. Please see the
    /// <see cref="Microsoft.Sdc.Tasks.Msi.MsiTaskBase.MsiLoggingOptions"/> documentation for
    /// further information.
    /// </para>
    /// <para><i>userInterfaceLevel</i></para>
    /// <para>
    /// The user interface level displayed by MsiExec during the installation. Possible values are: 
    /// None, Basic, ReducedWithModalDialog, FullWithModalDialog, NoneWithModalDialog, BasicWithModalDialog,
    /// BasicWithNoModalDialogs. The default is None. Please see the 
    /// <see cref="Microsoft.Sdc.Tasks.Msi.MsiTaskBase.MsiExecUILevel"/> documentation for
    /// further information.
    /// </para>
    /// <para><i>loadUserProfile</i></para>
    /// <para>
    /// <b>true</b> if the user's profile is loaded during the processing of the MSI, or <b>false</b> otherwise.
    /// This may be needed for some custom installation actions such as configuring ports and channels in 
    /// Microsoft BizTalk Server. The default is <b>false</b>.
    /// </para>
    /// <para><i>repairOptions</i></para>
    /// <para>
    /// A delimited list of the options for repairing the MSI. The possible values are: Default,
    /// ReinstallWhenFileMissing, ReinstallWhenOlderVersionInstalled, ReinstallWhenEqualOrOlderVersionInstalled,
    /// ReinstallWhenDifferentVersionInstalled, ReinstallWhenChecksumIncorrect, ForceReinstall,
    /// RewriteUserSpecificRegistry, RewriteComputerSpecificRegistry, OverwriteExistingShortcuts,
    /// RunFromSourceAndReCachePackage. The default is Default. Please see the
    /// <see cref="Microsoft.Sdc.Tasks.Msi.Repair.MsiRepairOptions"/> documentation for more
    /// information.
    /// </para>
    /// <para>
    /// <b>*</b> - Either <i>msiPath</i> or <i>productCode</i> must be provided. If both are provided then 
    /// <i>msiPath</i> takes precedence and <i>productCode</i> is ignored. If neither are provided then the
    /// task will throw an exception when it executes.
    /// </para>
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <Target Name="Test">
    ///         <Msi.Repair 
    ///             MsiPath="C:\Build\Msi\Packages\MyPackage.msi" 
    ///             ProductCode="b6b6f41c-fd3e-4b5b-8a73-c59494974990" 
    ///             LogFile="C:\Build\Msi\Packages\MyPackage.install.log" 
    ///             LogOptions="Default;Verbose" 
    ///             UserInterfaceLevel="None" 
    ///             LoadUserProfile="true" 
    ///             RepairOptions="Default;RewriteUserSpecificRegistry" />
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>
    #endregion
    public sealed class Repair : MsiTaskBase
    {
        #region Member Variables

        private string[] repairOptions = new string[] { MsiRepairOptions.Default.ToString() };

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Repair class.
        /// </summary>
        public Repair()
        {
        }

        #endregion

        #region Enumerations

        #region MsiRepairOptions Enumeration

        /// <summary>
        /// Options for repairing an installation. These values may be combined.
        /// </summary>
        [Flags]
        public enum MsiRepairOptions
        {
            /// <summary>
            /// Uses the default repair options.
            /// </summary>
            Default =
                MsiRepairOptions.ReinstallWhenFileMissing |
                MsiRepairOptions.ReinstallWhenEqualOrOlderVersionInstalled |
                MsiRepairOptions.ReinstallWhenChecksumIncorrect |
                MsiRepairOptions.RewriteComputerSpecificRegistry |
                MsiRepairOptions.OverwriteExistingShortcuts,
            /// <summary>
            /// Reinstall if a file is missing.
            /// </summary>
            ReinstallWhenFileMissing = 0x00000001,
            /// <summary>
            /// Reinstall if a file is missing or an older version of the product is already installed.
            /// </summary>
            ReinstallWhenOlderVersionInstalled = 0x00000002,
            /// <summary>
            /// Reinstall if a file is missing or an equal or older version of the product is already installed.
            /// </summary>
            ReinstallWhenEqualOrOlderVersionInstalled = 0x00000004,
            /// <summary>
            /// Reinstall if a file is missing or a different version of the product is already installed.
            /// </summary>
            ReinstallWhenDifferentVersionInstalled = 0x00000008,
            /// <summary>
            /// Reinstall if a file is missing or the stored checksum does not match the calculated value.
            /// </summary>
            ReinstallWhenChecksumIncorrect = 0x00000010,
            /// <summary>
            /// Force a reinstall of all files.
            /// </summary>
            ForceReinstall = 0x00000100,
            /// <summary>
            /// Rewrite all user-specicic registry entries.
            /// </summary>
            RewriteUserSpecificRegistry = 0x00001000,
            /// <summary>
            /// Rewrite computer-specific registry entries.
            /// </summary>
            RewriteComputerSpecificRegistry = 0x00002000,
            /// <summary>
            /// Overwrite all existing shortcuts.
            /// </summary>
            OverwriteExistingShortcuts = 0x00010000,
            /// <summary>
            /// Runs from the source and re-cache the local package.
            /// </summary>
            RunFromSourceAndReCachePackage = 0x00100000
        }

        #endregion

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the options for repairing the MSI.
        /// </summary>
        /// <value>
        /// The options for repairing the MSI.
        /// </value>
        public string[] RepairOptions
        {
            get
            {
                return (this.repairOptions == null) ? new string[0] : this.repairOptions;
            }
            set
            {
                this.repairOptions = value;
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Executes the task to repair the MSI.
        /// </summary>
        /// <summary>
/// Performs the action of this task.
/// </summary>
        protected override void InternalExecute()
        {
            string msiExecArguments = null;
            if (!string.IsNullOrEmpty(this.MsiPath))
            {
                msiExecArguments = this.GetRepairArguments(this.MsiPath);
            }
            else if (this.ProductCode != Guid.Empty)
            {
                msiExecArguments = this.GetRepairArguments(this.ProductCode);
            }
            else
            {
                throw new InvalidOperationException("Either the MSI path or the product code must be specified before the task can execute.");
            }
            this.RunMsiExecProcess(msiExecArguments);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Appends the repair argument string for MsiExec with the specified options.
        /// </summary>
        /// <param name="arguments">
        /// The string of arguments being built.
        /// </param>
        /// <param name="packageName">
        /// The name of the package to repair.
        /// </param>
        private void AppendRepairArgument(StringBuilder arguments, string packageName)
        {
            //parse the logging options
            MsiRepairOptions options = MsiRepairOptions.Default;
            if (this.repairOptions != null && this.repairOptions.Length > 0)
            {
                options = 0;
                foreach (string optionString in this.repairOptions)
                {
                    options |= (MsiRepairOptions)Enum.Parse(typeof(MsiRepairOptions), optionString, true);
                }
            }
            //the standard repair switch
            arguments.Append("/f");
            //repair options
            if ((options & MsiRepairOptions.ReinstallWhenFileMissing) > 0)
            {
                arguments.Append("p");
            }
            if ((options & MsiRepairOptions.ReinstallWhenOlderVersionInstalled) > 0)
            {
                arguments.Append("o");
            }
            if ((options & MsiRepairOptions.ReinstallWhenEqualOrOlderVersionInstalled) > 0)
            {
                arguments.Append("e");
            }
            if ((options & MsiRepairOptions.ReinstallWhenDifferentVersionInstalled) > 0)
            {
                arguments.Append("d");
            }
            if ((options & MsiRepairOptions.ReinstallWhenChecksumIncorrect) > 0)
            {
                arguments.Append("c");
            }
            if ((options & MsiRepairOptions.ForceReinstall) > 0)
            {
                arguments.Append("a");
            }
            if ((options & MsiRepairOptions.RewriteUserSpecificRegistry) > 0)
            {
                arguments.Append("u");
            }
            if ((options & MsiRepairOptions.RewriteComputerSpecificRegistry) > 0)
            {
                arguments.Append("m");
            }
            if ((options & MsiRepairOptions.OverwriteExistingShortcuts) > 0)
            {
                arguments.Append("s");
            }
            if ((options & MsiRepairOptions.RunFromSourceAndReCachePackage) > 0)
            {
                arguments.Append("v");
            }
            //the package name
            arguments.Append(" ");
            arguments.Append(this.EscapeArgument(packageName));
        }

        /// <summary>
        /// Builds the arguments to repair a product.
        /// </summary>
        /// <param name="productCode">
        /// The identifier of the product.
        /// </param>
        /// <returns>
        /// The argument string to repair the product.
        /// </returns>
        private string GetRepairArguments(Guid productCode)
        {
            string productCodeString = productCode.ToString(MsiTaskBase.GuidFormat, CultureInfo.InvariantCulture);
            return this.GetRepairArguments(productCodeString);
        }

        /// <summary>
        /// Builds the arguments to repair a product.
        /// </summary>
        /// <param name="packageName">
        /// The name of the package, e.g. <i>C:\Install\Source\MyPackage.msi</i>
        /// </param>
        /// <returns>
        /// The argument string to repair the product.
        /// </returns>
        private string GetRepairArguments(string packageName)
        {
            StringBuilder arguments = new StringBuilder();
            this.AppendRepairArgument(arguments, packageName);
            arguments.Append(" ");
            this.AppendLoggingArgument(arguments);
            arguments.Append(" ");
            this.AppendUserInterfaceLevelArgument(arguments);
            return arguments.ToString();
        }

        #endregion
    }
}
